package org.budo.mongo.jdbc.driver.statement;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.budo.jdbc.driver.exception.BudoJdbcDriverNotSupportedException;
import org.budo.jdbc.driver.statement.AbstractJdbcStatement;
import org.budo.mongo.jdbc.driver.BudoMongoJdbcConnection;
import org.budo.mongo.jdbc.driver.BudoMongoJdbcPreparedStatement;
import org.budo.mongo.jdbc.driver.BudoMongoJdbcResultSetMetaData;
import org.budo.mongo.jdbc.driver.parser.StatementParserUtil;
import org.budo.mongo.jdbc.driver.util.JsonUtil;

import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

/**
 * @author li
 */
public abstract class AbstractMongoStatement extends AbstractJdbcStatement {
    /**
     * Timestamp类型参数占位符前缀
     */
    private static final String TIMESTAMP_PLACEHOLDER_PREFIX = "Timestamp[#";

    /**
     * Timestamp类型参数占位符后缀
     */
    private static final String TIMESTAMP_PLACEHOLDER_SUFFIX = "]";

    /**
     * Timestamp类型参数占位符位数
     */
    private static final int TIMESTAMP_DIGIT = 2;

    /**
     * Timestamp类型参数最大个数
     */
    private static final int TIMESTAMP_COUNT_MAX = (int) Math.pow(10, TIMESTAMP_DIGIT) - 1;

    /**
     * Timestamp类型参数占位符格式
     */
    private static final String TIMESTAMP_INDEX_FORMAT = "%0" + TIMESTAMP_DIGIT + "d";
    public static Object NULL_VALUE = new Object() {
        public String toString() {
            return "null";
        }
    };

    public static String AGGREGATE_PIPELINE = "MongoStatement.AGGREGATE_PIPELINE";

    public static String WHERE = "MongoStatement.WHERE";

    public static String INSERT_VALUES = "MongoStatement.INSERT_VALUES";

    public static String UPDATE_VALUES = "MongoStatement.UPDATE_VALUES";

    public static String SORT = "MongoStatement.SORT";

    public static String PAGE = "MongoStatement.PAGE";

    public static String PAGE_SKIP = "MongoStatement.PAGE.SKIP";

    public static String PAGE_LIMIT = "MongoStatement.PAGE.LIMIT";

    private String collectionName;

    private String statementJson;

    private BudoMongoJdbcResultSetMetaData resultSetMetaData;

    private ResultSet resultSet;

    private int updateCount;

    protected MongoCollection getMongoCollection(BudoMongoJdbcPreparedStatement preparedStatement) {
        Connection connection;
        try {
            connection = preparedStatement.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        BudoMongoJdbcConnection budoMongoJdbcConnection = (BudoMongoJdbcConnection) connection;
        MongoDatabase mongoDatabase = budoMongoJdbcConnection.mongoDatabase();
        return mongoDatabase.getCollection(this.collectionName);
    }

    @SuppressWarnings("deprecation")
    protected DB getMongoDb(BudoMongoJdbcPreparedStatement preparedStatement) {
        Connection connection;
        try {
            connection = preparedStatement.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        BudoMongoJdbcConnection mongoConnection = (BudoMongoJdbcConnection) connection;
        MongoClient mongoClient = mongoConnection.mongoClient();
        String dbName = mongoConnection.mongoDatabase().getName();
        return mongoClient.getDB(dbName);
    }

    protected Map<String, Object> getStatementDocumentWithParameterValues(Map<Integer, Object> parameterMap) {
        return this.getStatementDocumentWithParameterValues(this.statementJson, parameterMap);
    }

    protected Map<String, Object> getStatementDocumentWithParameterValues(String statementJson, Map<Integer, Object> parameterMap) {
        List<Timestamp> timestampTemporary = new ArrayList<Timestamp>(); // 计数器
        String statementJsonWithParameterValues = this.statementJsonReplaceParameterValues(statementJson, parameterMap, timestampTemporary);
        Map<String, Object> statementDocument = (Map<String, Object>) JsonUtil.fromJsom(statementJsonWithParameterValues);

        statementDocument = (Map<String, Object>) this.replaceTimestampValue(statementDocument, timestampTemporary);

        return statementDocument;
    }

    /**
     * 将BasicDBObject中
     */
    private Object replaceTimestampValue(Object value, List<Timestamp> timestampTemporary) {
        if (null == value || null == timestampTemporary || timestampTemporary.isEmpty()) {
            return value;
        }

        if (value instanceof Map) {
            Map map = (Map) value;
            Set<Entry<String, Object>> entrySet = map.entrySet();
            for (Entry<String, Object> entry : entrySet) {
                Object entryValue = entry.getValue();
                Object replacement = this.replaceTimestampValue(entryValue, timestampTemporary); // 递归
                if (null != replacement && !replacement.equals(entryValue) && replacement instanceof Timestamp) { // 反回了一个 Timestamp
                    entry.setValue(replacement); // 替换值为 Timestamp
                }
            }
        } else if (value instanceof List) {
            List list = (List) value;
            for (int i = 0; i < list.size(); i++) {
                Object eachValue = list.get(i);
                Object replacement = this.replaceTimestampValue(eachValue, timestampTemporary); // 递归
                if (null != replacement && !replacement.equals(eachValue) && replacement instanceof Timestamp) { // 反回了一个 Timestamp
                    list.set(i, replacement); // 替换值为 Timestamp
                }
            }
        } else if (value instanceof Number) {
            return value; // 返回空或自己，不会替换
        } else if (value instanceof String) {
            String stringValue = (String) value;
            if (stringValue.startsWith(TIMESTAMP_PLACEHOLDER_PREFIX)) {
                int index = new Integer(stringValue.substring(11, 13));
                Timestamp timestamp = timestampTemporary.get(index);
                // timestampTemporary.remove(index); // 移除这个
                return timestamp; // 返回
            }
        } else {
            throw new RuntimeException("value=" + value + ", type=" + value.getClass());
        }
        return value;
    }

    /**
     * 将statementJson替换参数后返回
     * 
     * @param timestampTemporary
     * @param parameterMap
     */
    protected String statementJsonReplaceParameterValues(String statementJson, Map<Integer, Object> parameterMap, List<Timestamp> timestampTemporary) {
        if (null == statementJson || statementJson.isEmpty()) {
            return statementJson;
        }

        String result = new String(statementJson);

        int fromIndex = 0; // 上一次while循环已扫描到这个位置
        while (true) {
            int beginIndex = result.indexOf(StatementParserUtil.JDBC_PARAMETER_PREFIX, fromIndex);
            if (beginIndex < 0) {
                break;
            }

            int endIndex = result.indexOf(StatementParserUtil.JDBC_PARAMETER_SUFFIX, beginIndex + 1);
            String jdbcParameterIndex = result.substring(beginIndex + StatementParserUtil.JDBC_PARAMETER_PREFIX_LENGTH, endIndex);
            Integer parameterIndex = new Integer(jdbcParameterIndex);
            Object parameterValue = parameterMap.get(parameterIndex);

            if (null == parameterValue) {
                throw new RuntimeException("No value specified for parameter " + parameterIndex);
            } else if (NULL_VALUE.equals(parameterValue) || parameterValue instanceof Number || parameterValue instanceof Boolean) {
                result = result.substring(0, beginIndex - 1) + parameterValue + result.substring(endIndex + 3); // 无引号
            } else if (parameterValue instanceof String) {
                result = result.substring(0, beginIndex) + parameterValue + result.substring(endIndex + 2); // 有引号
            } else if (parameterValue instanceof Timestamp) {
                Integer timestampTemporaryIndex = timestampTemporary.size(); //
                if (timestampTemporaryIndex >= TIMESTAMP_COUNT_MAX) { // 最多允许TIMESTAMP_COUNT_MAX个Timestamp参数
                    throw new BudoJdbcDriverNotSupportedException("Timestamp 类型参数个数超过" + TIMESTAMP_COUNT_MAX + " , parameterValue=" + parameterValue + ", parameterValue.type=" + parameterValue.getClass());
                }
                timestampTemporary.add((Timestamp) parameterValue); //
                String timestampTemporaryIndexStr = String.format(TIMESTAMP_INDEX_FORMAT, timestampTemporaryIndex); // 转为 TIMESTAMP_DIGIT 位，不够在前面补0
                result = result.substring(0, beginIndex) + TIMESTAMP_PLACEHOLDER_PREFIX + timestampTemporaryIndexStr + TIMESTAMP_PLACEHOLDER_SUFFIX + result.substring(endIndex + 2); // 先作为字符串，转成BasicDBObject对象后再处理
            } else if (parameterValue instanceof Timestamp[]) {
                throw new RuntimeException("不支持 Timestamp[] 数组, parameterValue=" + parameterValue);
            } else {
                throw new BudoJdbcDriverNotSupportedException("parameterValue=" + parameterValue + ", parameterValue.type=" + parameterValue.getClass());
            }

            // 记录本轮已处理到的位置，下一轮从这里往后继续处理，因为替换过参数，长度有变化，所以不能从 endIndex开始
            fromIndex = beginIndex;
        }

        return result;
    }

    public BudoMongoJdbcResultSetMetaData getResultSetMetaData() {
        return this.resultSetMetaData;
    }

    public void setResultSetMetaData(BudoMongoJdbcResultSetMetaData resultSetMetaData) {
        this.resultSetMetaData = resultSetMetaData;
    }

    public void setStatementJson(String statementJson) {
        this.statementJson = statementJson;
    }

    public void setCollectionName(String collectionName) {
        this.collectionName = collectionName;
    }

    public ResultSet getResultSet() {
        return this.resultSet;
    }

    public void setResultSet(ResultSet resultSet) {
        this.resultSet = resultSet;
    }

    public int getUpdateCount() {
        return this.updateCount;
    }

    public void setUpdateCount(int updateCount) {
        this.updateCount = updateCount;
    }

    public String toString() {
        return super.toString() + ", statementJson=" + this.statementJson + ", collectionName=" + this.collectionName;
    }
}